﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Validator.Entities;

namespace Validator
{
    /// <summary>
    /// The Validator class serves to cross-check data inputs against one another in order to verify the validity of the data being received by the device.
    /// The class also sets logical maxiumum and minimum values for each of the inputs being collected, flagging instances in which unrealistic data is collected.
    /// </summary>
    class Manager
    {
        private MomentEntity current;       // Most recent Snapshot produced by the program.
        private ArrayList snapshotList;         // List of the five most recent Snapshots for calculation purposes.
        private double accelArea;               // Running tab on the area under the acceleration graph for numerical integration purposes.
        private double speedArea;               // Running tab on the area under the speed graph for numerical integration purposes.
        private int count;

        private const double RADIUS = 6371000;

        /// <summary>
        /// Default constructor, fills instance data with default values.
        /// </summary>
        public Manager()
        {
            current = null;
            snapshotList = new ArrayList();
            accelArea = 0;
            speedArea = 0;
            count = 0;
        }

        /// <summary>
        /// The set method fills current with the most recent Snapshot and updates a running list of the five most recent Snapshots.  From there, it calls internal 
        /// functions to check the ranges of the Snapshot values, ensures that the transition from the previous Snapshot to the current Snapshot makes sense, and cross-checks 
        /// the values from several inputs to verify accuracy.
        /// </summary>
        public MomentEntity Current
        {
            get
            {
                return current;
            }
            set
            {
                current = value;
                snapshotList.RemoveAt(0);
                snapshotList.Add(current);
                count++;

                checkRanges();
                checkTransition();
                crossCheck();
            }
        }

        /// <summary>
        /// This function checks to see if collected Snapshot values are within a reasonable range of expected values. Checks x-acceleration, 
        /// y-acceleration, z-acceleration, fuel level, heading, latitude, longitude, RPM, speed, throttle position, and VIN.
        /// </summary>
        public void checkRanges()
        {
            // Checks acceleration on the x-axis, assumes that acceleration should not be greater than 10 m/s^2 in either direction.
            if (current.XAxis >= 10.0 || current.XAxis <= -10.0)
            {
                Trace.TraceWarning("X-acceleration (" + current.XAxis + " m/s^2) is outside of reasonable range");
                current.FlawX = true;
            }
            // Checks acceleration on the y-axis, assumes that acceleration should not be greater than 10 m/s^2 in either direction. 
            if (current.YAxis >= 10.0 || current.YAxis <= -10.0)
            {
                Trace.TraceWarning("Y-acceleration (" + current.YAxis + " m/s^2) is outside of reasonable range");
                current.FlawY = true;
            }
            // Checks acceleration on the z-axis, assumes that acceleration should not vary more than 10 m/s^2 from -1g. 
            if (current.ZAxis >= 0.2 || current.ZAxis <= -19.8)
            {
                Trace.TraceWarning("Z-acceleration (" + current.ZAxis + " m/s^2) is outside of reasonable range");
                current.FlawZ = true;
            }
            // Checks that fuel level is a percentage value between 0 and 100.
            if (current.Fuel > 100 || current.Fuel < 0)
            {
                Trace.TraceWarning("Fuel level (" + current.Fuel + "%) is outside of possible range");
                current.FlawFuel = true;
            }
            // Checks that heading is a value between 0 and 360 degrees.
            if (current.Heading >= 360.0 || current.Heading < 0.0)
            {
                Trace.TraceWarning("Heading (" + current.Heading + " degrees) is outside of possible range");
                current.FlawHeading = true;
            }
            // Checks that latitude is a value between -90 and 90 degrees.
            if (current.Latitude >= 90.0 || current.Latitude < -90.0)
            {
                Trace.TraceWarning("Latitude (" + current.Latitude + " degrees) is outside of possible range");
                current.FlawLatitude = true;
            }
            // Checks that longitude is a value between -180 and 180 degrees.
            if (current.Longitude >= 180.0 || current.Longitude < -180.0)
            {
                Trace.TraceWarning("Longitude (" + current.Latitude + " degrees) is outside of possible range");
                current.FlawLongitude = true;
            }
            // Checks that speed is a value between 0 and 140 mph.
            if (current.Speed > 140.0 || current.Speed < 0.0)
            {
                Trace.TraceWarning("Speed (" + current.Speed + " degrees) is outside of reasonable range");
                current.FlawSpeed = true;
            }
            // Checks that throttle position is a value between 0.0 and 100.0 degrees.
            if (current.Throttle > 100.0 || current.Throttle < 0.0)
            {
                Trace.TraceWarning("Throttle position (" + current.Throttle + " degrees) is outside of possible range");
                current.FlawThrottle = true;
            }
            // Checks that VIN is an 11-digit value which contains no instances of the letters "I," "O," or "Q."
            if (current.VIN.Length != 11 || current.VIN.Contains("I") || current.VIN.Contains("O") || current.VIN.Contains("Q"))
            {
                Trace.TraceWarning("VIN (" + current.VIN + ") is outside of possible range");
            }
        }

        /// <summary>
        /// This function checks to see if the transition from the previous Snapshot to the current Snapshot was reasonable.  Checks changes in 
        /// x-acceleration, y-acceleration, z-acceleration, fuel level, latitude, longitude, and speed.
        /// </summary>
        public void checkTransition()
        {
            // If there are less than two Snapshots in the snapshotList, simply exit the function.
            if (snapshotList.Count < 2)
            {
                return;
            }
            // Otherwise, compare the two most recent snapshots to determine if the transition is reasonable.
            else
            {
                int currentIndex = snapshotList.Count - 1;
                MomentEntity previous = (MomentEntity)snapshotList[currentIndex - 1];

                // Check that the change in x-acceleration in the past second does not exceed 10 m/s^2.
                if (System.Math.Abs(current.XAxis - previous.XAxis) > 10.0)
                {
                    Trace.TraceWarning("The difference between the previous x-acceleration (" + previous.XAxis + " m/s^2) and the current x-acceleration " +
                        "(" + current.XAxis + " m/s^2) is unreasonable.");
                }
                // Check that the change in y-acceleration in the past second does not exceed 10 m/s^2.
                if (System.Math.Abs(current.YAxis - previous.YAxis) > 10.0)
                {
                    Trace.TraceWarning("The difference between the previous y-acceleration (" + previous.YAxis + " m/s^2) and the current y-acceleration " +
                        "(" + current.YAxis + " m/s^2) is unreasonable.");
                }
                // Check that the change in z-acceleration in the past second does not exceed 10 m/s^2.
                if (System.Math.Abs(current.ZAxis - previous.ZAxis) > 10.0)
                {
                    Trace.TraceWarning("The difference between the previous z-acceleration (" + previous.ZAxis + " m/s^2) and the current z-acceleration " +
                        "(" + current.ZAxis + " m/s^2) is unreasonable.");
                }
                // Check that the change in fuel level in the past second does not exceed 5% (the change should not be this great, but 
                // factors such as incline can have sudden dramatic effects on the fuel level reading).
                if (System.Math.Abs(current.Fuel - previous.Fuel) > 5.0)
                {
                    Trace.TraceWarning("The difference between the previous fuel level (" + previous.Fuel + "%) and the current fuel level " +
                        "(" + current.Fuel + "%) is unreasonable.");
                }
                // Check that the change in latitude in the past second is reasonable.
                if (System.Math.Abs(current.Latitude - previous.Latitude) > 0.00001)
                {
                    Trace.TraceWarning("The difference between the previous latitude (" + previous.Latitude + " degrees) and the current latitude " +
                        "(" + current.Latitude + " degrees) is unreasonable.");
                }
                // Check that the change in longitude in the past second is reasonable.
                if (System.Math.Abs(current.Longitude - previous.Longitude) > 0.00001)
                {
                    Trace.TraceWarning("The difference between the previous longitude (" + previous.Longitude + " degrees) and the current longitude " +
                        "(" + current.Longitude + " degrees) is unreasonable.");
                }
                // Check that the change in speed in the past second is greater than 50 mph (even the most sudden stops should not be this dramatic, barring
                // potential crashes which bring a vehicle to an instant halt).
                if (System.Math.Abs(current.Speed - previous.Speed) > 50)
                {
                    Trace.TraceWarning("The difference between the previous speed (" + previous.Speed + " mph) and the current speed " +
                        "(" + current.Speed + " mph) is unreasonable.");
                }
            }
        }

        /// <summary>
        /// Function which cross-checks the data received from the accelerometer, GPS, and OBD II modules.  Because the data being used here is discrete, not continuous, 
        /// it is not possible to differentiate the newest data point and receive information on the slope of that region of the dataset.  Therefore, the cross-checking process 
        /// will be one Snapshot behind the range check and the transition check.
        /// </summary>
        public void crossCheck()
        {
            checkAccelWithSpeed();
            checkAccelWithLocation();
            checkSpeedWithAccel();
            checkSpeedWithLocation();
            // checkLocationWithSpeed();
            // checkLocationWithAccel();
        }

        /// <summary>
        /// Cross-checks acceleration and speed.  Because the speed of the car is always directed forward, and the y-axis of the accelerometer is always directed forward
        /// as well, only the y-acceleration is needed.
        /// </summary>
        private void checkAccelWithSpeed()
        {
            // A minimum of three Snapshots are necessary to approximate an average slope.
            if (snapshotList.Count < 3)
            {
                return;
            }
            else
            {
                double currentSpeed = current.Speed;                                  // Speed of the current Snapshot.
                MomentEntity previous = (MomentEntity)snapshotList[snapshotList.Count - 3];     // Two Snapshots prior to the current Snapshot.
                double previousSpeed = previous.Speed;                                // Speed of the Snapshot two prior to the current Snapshot.

                double averageAcceleration = (currentSpeed - previousSpeed) / 2;
                MomentEntity check = (MomentEntity)snapshotList[snapshotList.Count - 2];        // Snapshot to be checked, one prior to current.

                if (System.Math.Abs(averageAcceleration - check.YAxis) > 3.0)
                {
                    Trace.TraceWarning("Acceleration calculated from speed (" + averageAcceleration + " m/s^2) is far from recorded acceleration (" + check.YAxis + " m/s^2).");
                }
            }
        }

        /// <summary>
        /// Cross-checks acceleration and location.
        /// </summary>
        private void checkAccelWithLocation()
        {
            if (snapshotList.Count < 5)
            {
                return;
            }
            else
            {
                // Create instance of each of the Snapshots stored in the Snapshot list.
                MomentEntity one = (MomentEntity)snapshotList[snapshotList.Count - 5];
                MomentEntity two = (MomentEntity)snapshotList[snapshotList.Count - 4];
                MomentEntity three = (MomentEntity)snapshotList[snapshotList.Count - 3];
                MomentEntity four = (MomentEntity)snapshotList[snapshotList.Count - 2];

                // Convert each of the necessary latitude and longitude coordinates into radians.
                double oneLatRad = one.Latitude * (System.Math.PI / 180);
                double oneLonRad = one.Longitude * (System.Math.PI / 180);
                double threeLatRad = three.Latitude * (System.Math.PI / 180);
                double threeLonRad = three.Longitude * (System.Math.PI / 180);
                double currentLatRad = current.Latitude * (System.Math.PI / 180);
                double currentLonRad = current.Longitude * (System.Math.PI / 180);

                // Find the distance between the third Snapshot and the first Snapshot using the Haversine formula.
                double temp1 = System.Math.Pow(System.Math.Sin((threeLatRad - oneLatRad) / 2), 2.0) + System.Math.Cos(oneLatRad) *
                    System.Math.Cos(threeLatRad) * System.Math.Pow(System.Math.Sin((threeLonRad - oneLonRad)), 2.0);
                double temp2 = 2 * System.Math.Atan2(System.Math.Sqrt(temp1), System.Math.Sqrt(1 - temp1));
                double distance1 = RADIUS * temp2;

                // Find the distance between the current Snapshot and the third Snapshot using the Haversine formula.
                temp1 = System.Math.Pow(System.Math.Sin((currentLatRad - threeLatRad) / 2), 2.0) + System.Math.Cos(threeLatRad) *
                    System.Math.Cos(currentLatRad) * System.Math.Pow(System.Math.Sin((currentLonRad - threeLonRad)), 2.0);
                temp2 = 2 * System.Math.Atan2(System.Math.Sqrt(temp1), System.Math.Sqrt(1 - temp1));
                double distance2 = RADIUS * temp2;

                // Find two sets of average velocities, one for the second Snapshot and one for the fourth, using the calculated distances.
                double averageVelocity1 = distance1 / 2;
                double averageVelocity2 = distance2 / 2;

                // Find an average acceleration for the third Snapshot using the two calculated average velocities.
                double averageAcceleration = (averageVelocity2 - averageVelocity1) / 2;

                if (System.Math.Abs(averageAcceleration - three.YAxis) > 3.0)
                {
                    Trace.TraceWarning("Acceleration calculated from location (" + averageAcceleration + " m/s^2) is far from recorded acceleration (" + three.YAxis + " m/s^2).");
                }
            }
        }

        /// <summary>
        /// Cross-checks speed and acceleration.  The method of numerical integration being used, Simpson's Rule, requires the function to be split into an even number of subsections.
        /// </summary>
        private void checkSpeedWithAccel()
        {
            if (snapshotList.Count < 2 || count % 2 == 0)
            {
                return;
            }
            else
            {
                MomentEntity one = (MomentEntity)snapshotList[snapshotList.Count - 3];
                MomentEntity two = (MomentEntity)snapshotList[snapshotList.Count - 2];
                MomentEntity three = (MomentEntity)snapshotList[snapshotList.Count - 1];

                // Add the approximate area under the first two subsections to a running tab.
                accelArea += (1 / 3) * (one.YAxis + 4 * two.YAxis + three.YAxis);

                if (System.Math.Abs(accelArea - three.Speed) > 10.0)
                {
                    Trace.TraceWarning("Speed calculated from acceleration (" + accelArea + " m/s) is far from recorded speed (" + three.Speed + " m/s).");
                }
            }
        }

        /// <summary>
        /// Cross-checks speed and location.
        /// </summary>
        private void checkSpeedWithLocation()
        {
            if (snapshotList.Count < 3)
            {
                return;
            }
            else
            {
                // Create instance of each of the Snapshots stored in the Snapshot list.
                MomentEntity one = (MomentEntity)snapshotList[snapshotList.Count - 3];
                MomentEntity two = (MomentEntity)snapshotList[snapshotList.Count - 2];
                MomentEntity three = (MomentEntity)snapshotList[snapshotList.Count - 1];

                // Convert each of the necessary latitude and longitude coordinates into radians.
                double oneLatRad = one.Latitude * (System.Math.PI / 180);
                double oneLonRad = one.Longitude * (System.Math.PI / 180);
                double threeLatRad = three.Latitude * (System.Math.PI / 180);
                double threeLonRad = three.Longitude * (System.Math.PI / 180);

                // Find the distance between the current Snapshot and the Snapshot two prior to the current using the Haversine formula.
                double temp1 = System.Math.Pow(System.Math.Sin((threeLatRad - oneLatRad) / 2), 2.0) + System.Math.Cos(oneLatRad) *
                    System.Math.Cos(threeLatRad) * System.Math.Pow(System.Math.Sin((threeLonRad - oneLonRad)), 2.0);
                double temp2 = 2 * System.Math.Atan2(System.Math.Sqrt(temp1), System.Math.Sqrt(1 - temp1));
                double distance1 = RADIUS * temp2;

                // Find the average velocity.
                double averageVelocity = distance1 / 2;

                if (System.Math.Abs(averageVelocity - three.Speed) > 10.0)
                {
                    Trace.TraceWarning("Speed calculated from location (" + averageVelocity + " m/s) is far from recorded speed (" + three.Speed + " m/s).");
                }
            }
        }

        /// <summary>
        /// Cross-checks location and speed.
        /// </summary>
        private void checkLocationWithSpeed()
        {
            // Not currently possible.
            throw new NotImplementedException();
        }

        /// <summary>
        /// Cross-checks location and acceleration.
        /// </summary>
        private void checkLocationWithAccel()
        {
            // Not currently possible.
            throw new NotImplementedException();
        }
    }
}
